package org.oimwrapper.connectors.internal;

import Thor.API.*;
import Thor.API.Exceptions.*;
import Thor.API.Operations.*;

import com.thortech.util.logging.*;
import com.thortech.xl.dataaccess.*;
import com.thortech.xl.util.config.*;
import java.text.*;
import java.util.*;import org.oimwrapper.connectors.exceptions.*;

/**
 * This provides common functions for managing users.
 * 
 * THIS IS AN INTERNAL CLASS AND SHOULD NOT BE USED DIRECTLY.
 */
public final class OIMUserUtilities {

	/**
	 * The default logger instance for this instance.
	 */
	private Logger logger;

	/**
	 * The user operations instance that backs this object.
	 */
	private tcUserOperationsIntf userOp;

	/**
	 * The password operations instance that backs this object.
	 */
	private tcPasswordOperationsIntf passwordOp;
	
	/**
	 * The property operations instance that backs this object.
	 */
	private tcPropertyOperationsIntf propertyOp;
	
	/**
	 * The recon operations instance that backs this object.
	 */
	private tcReconciliationOperationsIntf reconOp;
	
	/**
	 * The data provider used by this object.
	 */
	private tcDataProvider database;

	/**
	 * The default constructor.
	 * 
	 * @param ClassLogger The Logger used by the invoking class.
	 * @param UserOp The user operations utility from the invoking class.
	 * @param PasswordOp The password operations utility from the invoking class.
	 * @param PropertyOp The property operations utility used by the invoking class.
	 * @param ReconOp The recon operations instance that backs this object.
	 * @param Database The data provider from the invoking class.
	 */
	protected OIMUserUtilities(Logger ClassLogger, tcUserOperationsIntf UserOp, tcPasswordOperationsIntf PasswordOp, tcPropertyOperationsIntf PropertyOp, tcReconciliationOperationsIntf ReconOp, tcDataProvider Database) {
		logger = ClassLogger;
		userOp = UserOp;
		passwordOp = PasswordOp;
		propertyOp = PropertyOp;
		reconOp = ReconOp;
		database = Database;
	}

	/**
	 * Creates an OIM user.
	 *
	 * @param Username The username for the new user.
	 * @param FirstName The first name for the new user.
	 * @param LastName The last name for the new user.
	 * @param Password The password for the new user.
	 * @param OrgKey The instance key of the organization to place the new user in.
	 * @param Role The employee role for the new user.
	 * @param Type The account type for the new user.
	 * @exception Exception
	 */
	public void createUser(String Username, String FirstName, String LastName, String Password, long OrgKey, String Role, String Type) throws Exception {
		logger.debug("Entering userUtilities.createUser()");
		Map data = new HashMap();
		data.put("Users.User ID", Username);
		data.put("Users.First Name", FirstName);
		data.put("Users.Last Name", LastName);
		data.put("Users.Password", Password);
		data.put("Organizations.Key", Long.toString(OrgKey));
		data.put("Users.Role", Role);
		data.put("Users.Xellerate Type", Type);
		userOp.createUser(data);
		logger.debug("Exiting userUtilities.createUser()");
	}

	/**
	 * Creates an OIM user.
	 *
	 * @param UserData A Map containing the attributes and values for the new user.
	 * @exception Exception
	 */
	public void createUser(Map UserData) throws Exception {
		logger.debug("Entering userUtilities.createUser()");
		userOp.createUser(UserData);
		logger.debug("Exiting userUtilities.createUser()");
	}

	/**
	 * Disables an OIM user.
	 *
	 * @param UserKey The key of the user instance to disable.
	 * @exception Exception
	 */
	public void disableUser(long UserKey) throws Exception {
		logger.debug("Entering userUtilities.disableUser()");
		if (!isUserDisabled(UserKey)) {
			userOp.disableUser(UserKey);
		}
		logger.debug("Exiting userUtilities.disableUser()");
	}

	/**
	 * Enables an OIM user.
	 *
	 * @param UserKey The key of the user instance to enable.
	 * @exception Exception
	 */
	public void enableUser(long UserKey) throws Exception {
		logger.debug("Entering userUtilities.enableUser()");
		if (isUserDisabled(UserKey)) {
			userOp.enableUser(UserKey);
		}
		logger.debug("Exiting userUtilities.enableUser()");
	}

	/**
	 * Deletes an OIM user.
	 *
	 * @param UserKey The key of the user instance to delete.
	 * @exception Exception
	 */
	public void deleteUser(long UserKey) throws Exception {
		logger.debug("Entering userUtilities.deleteUser()");
		if (!isUserRevoked(UserKey)) {
			userOp.deleteUser(UserKey);
		}
		logger.debug("Exiting userUtilities.deleteUser()");
	}

	/**
	 * Gets the instance key for a user, or 0 if the user is not found.
	 *
	 * @param Name The name of the user to get the instance key for.
	 * @return The instance key of the user, or 0 if the user is not found.
	 * @exception Exception
	 */
	public long getUser(String Name) throws Exception {
		logger.debug("Entering userUtilities.getUser()");
		long result = 0L;
		Map searchFor = new HashMap();
		searchFor.put("Users.User ID", Name == null ? "" : Name.toUpperCase());
		tcResultSet results = userOp.findUsers(searchFor);
		if (results.getRowCount() == 1) {
			results.goToRow(0);
			result = results.getLongValue("Users.Key");
			logger.debug("Found " + Name + " = " + Long.toString(result));
		}
		logger.debug("Exiting userUtilities.getUser()");
		return result;
	}

	/**
	 * Given tuple to search for, this function returns a Map of attributes and values for 
	 * the resulting item.   An empty Map is returned if the object is not found.
	 *
	 * @param Attribute The name of the to search for.
	 * @param Value The value of the attribute to search for.
	 * @param HideEncrypted <code>true</code> will mask out any encrypted values from the result set,
	 *                      and <code>false</code> will allow the encypted values to pass through.
	 * @return A Map containing the attribute value pairs for the resulting object, or an
	 *         empty Map if nothing is found.
	 * @exception Exception
	 */
	public Map getUserData(String Attribute, String Value, boolean HideEncrypted) throws Exception {
		logger.debug("Entering userUtilities.getUserData()");
		Map result = null;
		Map[] results = getUsersData(Attribute, Value, HideEncrypted);
		if (results.length == 0) {
			throw new OIMKeyNotFoundException("user : " + Attribute, Value);
		} else if (results.length == 1) {
			result = results[0];
		} else {
			throw new OIMMultipleMatchesFoundException("user", Attribute, Value);
		}
		logger.debug("Exiting userUtilities.getUserData()");
		return result;
	}

	/**
	 * Given tuple to search for, this function returns an Array of Maps of attributes and 
	 * values for the resulting item.   An empty Array is returned if the object is not found.
	 *
	 * @param Attribute The name of the to search for.
	 * @param Value The value of the attribute to search for.
	 * @param HideEncrypted <code>true</code> will mask out any encrypted values from the result set,
	 *                      and <code>false</code> will allow the encypted values to pass through.
	 * @return A Array of Map's containing the attribute value pairs for the resulting object, or an
	 *         empty Array if nothing is found.
	 * @exception Exception
	 */
	public Map[] getUsersData(String Attribute, String Value, boolean HideEncrypted) throws Exception {
		logger.debug("Entering userUtilities.getUsersData()");
		Map searchFor = new HashMap();
		searchFor.put(Attribute, Value);
		Map[] result = getUsersData(searchFor, HideEncrypted);
		logger.debug("Exiting userUtilities.getUsersData()");
		return result;
	}

	/**
	 * Given tuple to search for, this function returns an Array of Maps of attributes and 
	 * values for the resulting item.   An empty Array is returned if the object is not found.
	 *
	 * @param SearchFor A collection of zero or more attribute-value tuples to search for.
	 * @param HideEncrypted <code>true</code> will mask out any encrypted values from the result set,
	 *                      and <code>false</code> will allow the encypted values to pass through.
	 * @return A Array of Map's containing the attribute value pairs for the resulting object, or an
	 *         empty Array if nothing is found.
	 * @exception Exception
	 */
	public Map[] getUsersData(Map SearchFor, boolean HideEncrypted) throws Exception {
		logger.debug("Entering userUtilities.getUsersData()");
		tcResultSet results = userOp.findUsers(SearchFor);
		Map[] result = new HashMap[results.getRowCount()]; 
		for (int i = 0; i < results.getRowCount(); i++) {
			results.goToRow(i);
			result[i] = new HashMap();
			String[] columns = results.getColumnNames();
			for (int j = 0; j < columns.length; j++) {
				if (HideEncrypted && (columns[j].equalsIgnoreCase("Users.Identity") || columns[j].equalsIgnoreCase("Users.Password"))) {
					result[i].put(columns[j], "********");
				} else {
					result[i].put(columns[j], results.getStringValue(columns[j]));
				}
			}
		}
		logger.debug("Exiting userUtilities.getUsersData()");
		return result;
	}

	/**
	 * Gets an array of Maps containing data for all users on the system.
	 *
	 * @param HideEncrypted <code>true</code> will mask out any encrypted values from the result set,
	 *                      and <code>false</code> will allow the encypted values to pass through.
	 * @return A Array of Map's containing the attribute value pairs for the resulting object.
	 * @exception Exception
	 */
	public Map[] getAllUsers(boolean HideEncrypted) throws Exception {
		logger.debug("Entering userUtilities.getAllUsers()");
		Map[] result = getUsersData(new HashMap(), HideEncrypted);
		logger.debug("Exiting userUtilities.getAllUsers()");
		return result;
	}

	/**
	 * Sets an attribute on an OIM user.
	 *
	 * @param UserKey The user instance key of the user to set the attribute on.
	 * @param Attribute The name of attribute to set.  For system provided columns, this is generally a field lookup, 
	 *                  for example <code>Users.Email</code>.  For user defined fields, this is the column name, for 
	 *                  example <code>USR_UDF_MYITEM</code>.
	 * @param Value The value to set for the specified attribute.
	 * @exception Exception
	 */
	public void setUserAttribute(long UserKey, String Attribute, String Value) throws Exception {
		logger.debug("Entering userUtilities.setUserAttribute()");
		if (Attribute.equalsIgnoreCase("Users.Password")) {
			setUserPassword(UserKey, Value);
		} else {
			Map searchFor = new HashMap();
			searchFor.put("Users.Key", Long.toString(UserKey));
			tcResultSet results = userOp.findUsers(searchFor);
			if (results.getRowCount() == 1) {
				Map data = new HashMap();
				data.put(Attribute, Value == null ? "" : Value);
				userOp.updateUser(results, data);
			} else {
				throw new OIMKeyNotFoundException("user", UserKey);
			}
		}
		logger.debug("Exiting userUtilities.setUserAttribute()");
	}
	
	/**
	 * Sets an attribute on an OIM user.
	 *
	 * @param UserKey The user instance key of the user to set the attributes on.
	 * @param Data A Map of attributes and values to set for the user.
	 * @exception Exception
	 */
	public void setUserAttributes(long UserKey, Map Data) throws Exception {
		logger.debug("Entering userUtilities.setUserAttributes()");
		if (Data.keySet().size() > 0) {
			Map searchFor = new HashMap();
			searchFor.put("Users.Key", Long.toString(UserKey));
			tcResultSet results = userOp.findUsers(searchFor);
			if (results.getRowCount() == 1) {
				userOp.updateUser(results, Data);
			} else {
				throw new OIMKeyNotFoundException("user", UserKey);
			}
		}
		logger.debug("Exiting userUtilities.setUserAttributes()");
	}

	/**
	 * Gets a list of groups that the user is a member of.
	 *
	 * @param UserKey The user instance key of the user to get the group membership for.
	 * @return An array of zero or more group names.
	 * @exception Exception
	 */
	public String[] getGroupsForUser(long UserKey) throws Exception {
		logger.debug("Entering userUtilities.getGroupsForUser()");
		tcResultSet groups = userOp.getGroups(UserKey);
		String[] result = new String[groups.getRowCount()];
		for (int i = 0; i < groups.getRowCount(); i++) {
			groups.goToRow(i);
			result[i] = groups.getStringValue("Groups.Group Name");
		}
		logger.debug("Exiting userUtilities.getGroupsForUser()");
		return result;
	}

	/**
	 * Check if the specified user account is disabled.
	 *
	 * @param UserKey The user instance key of the user to check for disablement.
	 * @return <code>true</code> if the user is disabled, or <code>false</code> if the user is not.
	 * @exception Exception
	 */
	public boolean isUserDisabled(long UserKey) throws Exception {
		logger.debug("Entering userUtilities.isUserDisabled()");
		Map data = getUserData("Users.Key", Long.toString(UserKey), false);
		boolean result = ((String) data.get("Users.Disable User")).equalsIgnoreCase("1");
		logger.debug("Exiting userUtilities.isUserDisabled()");
		return result;
	}

	/**
	 * Check if the specified user account is revoked.
	 *
	 * @param UserKey The user instance key of the user to check for being revoked.
	 * @return <code>true</code> if the user is revoked, or <code>false</code> if the user is not.
	 * @exception Exception
	 */
	public boolean isUserRevoked(long UserKey) throws Exception {
		logger.debug("Entering userUtilities.isUserRevoked()");
		Map data = getUserData("Users.Key", Long.toString(UserKey), false);
		boolean result = ((String) data.get("Users.Status")).equalsIgnoreCase("Deleted");
		logger.debug("Exiting userUtilities.isUserRevoked()");
		return result;
	}

	/**
	 * Gets details on the proxy for a user.  An empty Map is returned if the proxy is not found.
	 *
	 * @param UserKey The key of the user to find the proxy for,
	 * @param HideEncrypted <code>true</code> will mask out any encrypted values from the result set,
	 *                      and <code>false</code> will allow the encypted values to pass through.
	 * @return A Map containing the attribute value pairs for the resulting object, or an
	 *         empty Map if nothing is found.
	 * @exception Exception
	 */
	public Map getProxyData(long UserKey, boolean HideEncrypted) throws Exception {
		logger.debug("Entering userUtilities.getProxyData()");
		Map result = new HashMap();
		tcResultSet details = userOp.getProxyDetails(UserKey);
		if (details.getRowCount() == 1) {
			details.goToRow(0);
			result = getUserData("Users.Key", details.getStringValue("Proxy.Proxy User Key"), HideEncrypted);
		}
		logger.debug("Exiting userUtilities.getProxyData()");
		return result;
	}

	/**
	 * Sets one user as a the proxy of another.
	 *
	 * @param UserKey The key of the user to set the proxy for.
	 * @param ProxyUserKey The key of the user to set as the proxy.
	 * @param StartDate The date on which the proxy will start.
	 * @param EndDate The date on which the proxy will end.
	 * @exception Exception
	 */
	public void setProxy(long UserKey, long ProxyUserKey, Date StartDate, Date EndDate) throws Exception {
		logger.debug("Entering userUtilities.setProxy()");
		userOp.setProxyForUser(UserKey, ProxyUserKey, StartDate, EndDate);
		logger.debug("Exiting userUtilities.setProxy()");
	}

	/**
	 * Sets the password for an OIM user
	 *
	 * @param UserKey The key of the user to set the password for.
	 * @param Password The password to set for the user.
	 * @exception Exception
	 */
	public void setUserPassword(long UserKey, String Password) throws Exception {
		logger.debug("Entering userUtilities.setUserPassword()");
		passwordOp.setXelleratePassword(UserKey, Password);
		Map searchFor = new HashMap();
		searchFor.put("Users.Key", Long.toString(UserKey));
		tcResultSet results = userOp.findUsers(searchFor);
		if (results.getRowCount() == 1) {
			Map changes = new HashMap();
			changes.put("Users.Change Password At Next Logon", "0");
			changes.put("Users.Password Must Change", "0");
			userOp.updateUser(results, changes);
		} else {
			throw new OIMKeyNotFoundException("user", UserKey);
		}
		logger.debug("Exiting userUtilities.setUserPassword()");
	}

	/**
	 * Gets a map of question and answer values for the specified user.
	 *
	 * @param UserKey The key of the user to get the question and answers for.
	 * @return A Map containing the question as the key, and the answer as the value.
	 * @exception Exception 
	 */
	public Map getChallengeQuestionsAndResponses(long UserKey) throws Exception {
		logger.debug("Entering userUtilities.getChallengeQuestionsAndResponses()");
		Map result = new HashMap();
		try {
			tcResultSet results = userOp.getChallengeValuesForUser(UserKey);
			for (int  i = 0; i < results.getRowCount(); i++) {
				results.goToRow(i);
				String question = results.getStringValue("Users.Password Challenge Question.Question");
				String answer = results.getStringValue("Users.Password Challenge Question.Answer");
				result.put(question, answer);
			}
		} catch (tcQuestionsNotDefinedException e) { ; }
		logger.debug("Exiting userUtilities.getChallengeQuestionsAndResponses()");
		return result;
	}

	/**
	 * Gets all the user keys of the users matching the specified status.
	 *
	 * @param Status The status of users to search for.
	 * @return An array of user keys of the users matching the specified status.
	 * @exception Exception 
	 */
	public long[] getUserKeys(String Status) throws Exception {
		logger.debug("Entering userUtilities.getUserKeys()");
		Map searchFor = new HashMap();
		searchFor.put("Users.Status", Status);
		tcResultSet results = userOp.findUsersFiltered(searchFor, new String[] { "Users.Key" });
		long[] result = new long[results.getRowCount()];
		for (int i = 0; i < results.getRowCount(); i++) {
			results.goToRow(i);
			result[i] = results.getLongValue("Users.Key");
		}
		logger.debug("Exiting userUtilities.getUserKeys()");
		return result;
	}
	
	/**
	 * Gets all of the user keys of the users matching the specified filter.
	 *
	 * @param SqlFilter The an SQL filter to use when searching for users.
	 * @return An array of user keys of the users matching the specified status.
	 * @exception Exception 
	 */
	public long[] getFilteredUserKeys(String SqlFilter) throws Exception {
		logger.debug("Entering userUtilities.getFilteredUserKeys()");
		tcDataSet dataSet = new tcDataSet();
		dataSet.setQuery(database, "select usr_key from usr where " + SqlFilter);
		dataSet.executeQuery();
		tcResultSet results = new tcMetaDataSet(dataSet.getDataSetData(), database);
		long[] result = new long[results.getRowCount()];
		for (int i = 0; i < results.getRowCount(); i++) {
			results.goToRow(i);
			result[i] = results.getLongValue("Users.Key");
		}
		logger.debug("Exiting userUtilities.getFilteredUserKeys()");
		return result;
	}

	/**
	 * Gets the value of a specific attribute for a user.
	 *
	 * @param UserKey The key of the user to retrieve the value for.
	 * @param Attribute The name of the attribute to retrieve the value for.
	 * @return The value of the attribute, or an empty string if the attribute is not found.
	 * @exception Exception 
	 */
	public String getUserAttribute(long UserKey, String Attribute) throws Exception {
		logger.debug("Entering userUtilities.getUserAttribute()");
		String result = "";
		Map searchFor = new HashMap();
		searchFor.put("Users.Key", Long.toString(UserKey));
		tcResultSet results = userOp.findUsersFiltered(searchFor, new String[] { Attribute });
		if (results.getRowCount() == 1) {
			results.goToRow(0);
			result = results.getStringValue(Attribute);
		} else {
			throw new OIMKeyNotFoundException("user", UserKey);
		}
		logger.debug("Exiting userUtilities.getUserAttribute()");
		return result;
	}
	
	/**
	 * Sets a Map of question and answer values for the specified user.
	 *
	 * @param UserKey The key of the user to get the question and answers for.
	 * @param QuestionsAndAnswers A Map of the questions and responses to set for the user.
	 * @exception Exception 
	 */
	public void setChallengeQuestionsAndResponses(long UserKey, Map QuestionsAndAnswers) throws Exception {
		logger.debug("Entering userUtilities.setChallengeQuestionsAndResponses()");
		int numberRequired = Integer.parseInt(propertyOp.getPropertyValue("PCQ.NO_OF_QUES"));
		if (QuestionsAndAnswers.keySet().size() < numberRequired) {
			throw new OIMInsufficientResponseCountException(numberRequired);
		}
		ConfigurationClient client = ConfigurationClient.getConfigurationClient();
		Properties env = client.getComplexSetting("Discovery.CoreServer").getAllSettings();
		tcDataSet dataSet = new tcDataSet();
		dataSet.setQuery(database, "select usr_login, usr_password from usr where usr_key=" + Long.toString(UserKey));
		dataSet.executeQuery();
		tcResultSet results = new tcMetaDataSet(dataSet.getDataSetData(), database);
		if (results.getRowCount() == 1) {
			results.goToRow(0);
			String username = results.getStringValue("Users.User ID");
			String password = results.getStringValue("Users.Password");
			tcUtilityFactory.setChallengeValuesForUser(env, username, password, QuestionsAndAnswers);
		} else {
			throw new OIMKeyNotFoundException("user", UserKey);
		}
		logger.debug("Exiting userUtilities.setChallengeQuestionsAndResponses()");
	}
	
	/**
	 * Checks if a user has set his challenge questions and responses.
	 * 
	 * @param UserKey The key of the user to check.
	 * @return <code>true</code> if the challenge questions are set, or <code>false</code>
	 *         if the responses have not been set yet.
	 * @exception Exception
	 */
	public boolean challengeResponsesSet(long UserKey) throws Exception {
		logger.debug("Entering userUtilities.challengeResponsesSet()");
		boolean result = false;
		try {
			int numberRequired = Integer.parseInt(propertyOp.getPropertyValue("PCQ.NO_OF_QUES"));
			tcDataSet dataSet = new tcDataSet();
			dataSet.setQuery(database, "select pcq_key from pcq where usr_key=" + Long.toString(UserKey));
			dataSet.executeQuery();
			result = (dataSet.getRowCount() >= numberRequired);
		} catch (Exception e) {
			logger.warn("Direct query failed; using slow search -- " + e.getMessage());
			try {
				tcResultSet results = userOp.getChallengeValuesForUser(UserKey);
				result = results.getRowCount() > 0;
			} catch (tcQuestionsNotDefinedException notDefError) { ; }
		}
		logger.debug("Exiting userUtilities.challengeResponsesSet()");
		return result;
	}
	
	/**
	 * Checks if a user is required to change his password.
	 * 
	 * @param UserKey The key of the user to check.
	 * @return <code>true</code> if the password needs to be changed, or 
	 *         <code>false</code> if the password does not need to be changed.
	 * @exception Exception
	 */
	public boolean passwordChangeRequired(long UserKey) throws Exception {
		logger.debug("Entering userUtilities.passwordChangeRequired()");
		boolean result = false;
		Map searchFor = new HashMap();
		searchFor.put("Users.Key", Long.toString(UserKey));
		tcResultSet results = userOp.findUsersFiltered(searchFor, new String[] {"Users.Password Expiration Date" , "Users.Change Password At Next Logon"});
		if (results.getRowCount() == 1) {
			results.goToRow(0);
			result |= results.getStringValue("Users.Change Password At Next Logon").equals("1");
			String expiration = results.getStringValue("Users.Password Expiration Date");
			if (expiration.length() > 0) {
				DateFormat format = new SimpleDateFormat("yyyy-MM-dd");
				result |= format.parse(expiration).before(new Date());
			}
		} else {
			throw new OIMKeyNotFoundException("user", UserKey);
		}
		logger.debug("Exiting userUtilities.passwordChangeRequired()");
		return result;
	}
	
	/**
	 * Checks that question and answers responses for a user are sufficient.
	 * 
	 * @param UserKey The key of the user to check the responses for.
	 * @param QuestionsAndAnswers A Map of the question and answer response for the user.
	 * @return <code>true</code> if the responses are considered acceptable, or
	 *         <code>false</code> if the response are not acceptable.
	 * @exception Exception
	 */
	public boolean checkChallengeResponses(long UserKey, Map QuestionsAndAnswers) throws Exception {
		logger.debug("Entering userUtilities.checkChallengeResponses()");
		boolean result = false;
		Map responses = getChallengeQuestionsAndResponses(UserKey);
		int numberRequired = Integer.parseInt(propertyOp.getPropertyValue("PCQ.NO_OF_CORRECT_ANSWERS"));
		int numberCorrect = 0;
		for (Iterator iterator = QuestionsAndAnswers.keySet().iterator(); iterator.hasNext(); ) {
			String question = (String) iterator.next();
			String givenAnswer = ((String) QuestionsAndAnswers.get(question)).trim();
			String expectedAnswer = ((String) responses.get(question)).trim();
			if (givenAnswer.equalsIgnoreCase(expectedAnswer)) {
				numberCorrect++;
			}
		}
		result = numberCorrect >= numberRequired;
		logger.debug("Exiting userUtilities.checkChallengeResponses()");
		return result;
	}
	
	/**
	 * Sets an attribute on an OIM user via a recon event.
	 *
	 * @param UserKey The user instance key of the user to set the attributes on.
	 * @param Data A Map of attributes and values to set for the user.
	 * @exception Exception
	 */
	public void setUserAttributesAsync(long UserKey, Map Data) throws Exception {
		logger.debug("Entering userUtilities.setUserAttributesAsync()");
		if (Data.keySet().size() > 0) {
			Map userData = getUserData("Users.Key", Long.toString(UserKey), false);
			Map reconData = new HashMap();
			tcDataSet dataSet = new tcDataSet();
			dataSet.setQuery(database, "select orf_fieldname, prf_columnname, lku_type_string_key from orf inner join obj on orf.obj_key = obj.obj_key inner join prf on orf.orf_key = prf.orf_key left outer join lku on prf.prf_columnname = lku.lku_field where obj_name = 'Xellerate User'");
			dataSet.executeQuery();
			tcResultSet fields = new tcMetaDataSet(dataSet.getDataSetData(), database);
			if (fields.getRowCount() == 0) {
				throw new IllegalArgumentException("No reconciliation fields for Xellerate User are defined.");
			}
			for (int i = 0; i < fields.getRowCount(); i++) {
				fields.goToRow(i);
				String attribute = fields.getStringValue("Process Definition.Reconciliation Fields Mappings.ColumnName");
				if (!fields.isNull("Lookup Definition.Code")) {
					attribute = fields.getStringValue("Lookup Definition.Code");
				}
				String reconField = fields.getStringValue("Objects.Reconciliation Fields.Name");
				if (Data.get(attribute) != null) {
					reconData.put(reconField, Data.get(attribute));
				} else if (userData.get(attribute) != null) {
					reconData.put(reconField, userData.get(attribute));
				}
			}
			if (!reconOp.ignoreEvent("Xellerate User", reconData)) {
				long eventKey = reconOp.createReconciliationEvent("Xellerate User", reconData, false);
				reconOp.finishReconciliationEvent(eventKey);
			}
		}
		logger.debug("Exiting userUtilities.setUserAttributesAsync()");
	}
	
	/**
	 * Unlocks a user account.
	 * 
	 * @param UserKey The key of the user to unlock.
	 * @exception Exception
	 */
	public void unlockUser(long UserKey) throws Exception {
		logger.debug("Entering userUtilities.unlockUser()");
		int count = database.writeStatement("update usr set usr_locked = 0, usr_login_attempts_ctr = 0, usr_pwd_reset_attempts_ctr = 0 where usr_key = " + Long.toString(UserKey));
		if (count != 1) {
			throw new OIMKeyNotFoundException("user", UserKey);
		}
		database.commitTransaction();
		logger.debug("Exiting userUtilities.unlockUser()");
	}
	
	/**
	 * Forces an access policy re-evaluation for a user.
	 * 
	 * @param UserKey The key of the user.
	 * @exception Exception
	 */
	public void evaluatePolicies(long UserKey) throws Exception {
		logger.debug("Entering userUtilities.evaluatePolicies()");
		setUserAttribute(UserKey, "USR_POLICY_UPDATE", "1");
		logger.debug("Exiting userUtilities.evaluatePolicies()");
	}
}